package com.lipaluma.csv.io;

import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.util.ArrayList;
import java.util.List;

import org.apache.commons.lang.StringUtils;

import com.lipaluma.exceptions.CsvParserException;
import com.lipaluma.io.Row;
import com.lipaluma.io.SourceWriter;

/**
 * write a CSV file
 * 
 * @author mario.soares
 * <br/>
 * <b>inspired by :</b><br/>
 * @author Kasper B. Graversen from super-csv : class AbstractCsvWriter
 * @author James Bassett from super-csv : class AbstractCsvWriter
 */
public class CsvWriter implements SourceWriter {

	private final Writer writer;
	private final Character delimiter;
	private final Character quote;

	private List<CsvWriteRow> rows = new ArrayList<CsvWriteRow>();
	
	/**
	 * create a CSVWriter by a Writer
	 * 
	 * @param writer writer of the output file
	 * @param delimiter the delimiter
	 * @param quote the optional quote
	 */
	public CsvWriter(Writer writer, Character delimiter, Character quote) {
		this.writer = writer;
		this.delimiter = delimiter;
		this.quote = quote;
	}

	/**
	 * create a CSVWriter by an OutputStream
	 * 
	 * @param out OutputStream of the output file
	 * @param delimiter the delimiter
	 * @param quote the optional quote
	 */
	public CsvWriter(OutputStream out, Character delimiter, Character quote) {
		this.writer = new OutputStreamWriter(out);
		this.delimiter = delimiter;
		this.quote = quote;
	}

	public int getCurrentNumber() {
		return rows.size();
	}
	
	public Row createNewRow() {
		CsvWriteRow csvRow = new CsvWriteRow();
		rows.add(csvRow);
		return csvRow;
	}

	
	@Override
	public Row duplicateRow(Row row) {
		CsvWriteRow csvRow = new CsvWriteRow((CsvWriteRow)row);
		rows.add(csvRow);
		return csvRow;
	}
	
	public void close() {
		try {
			for (CsvWriteRow row : rows) {
				for (int i=0; i< row.getColumnNumbers(); i++) {
					if(i > 0)
						writer.append(delimiter);
					writer.append(escapeString(row.extractValueOnIndex(i)));
				}
				writer.append("\n");
			}
			
			writer.close();
		} catch (IOException e) {
			throw new CsvParserException("An error occured on writing the CSV file ", e);
		}
	}
	/**
	 * Make a string ready for writing by escaping various characters as specified by the CSV format. This method also
	 * updates the current lineNumber as newlines are encountered in the String to be escaped.
	 * 
	 * @param csvElement
	 *            an element of a CSV file
	 * @return an escaped version of the element ready for persisting
	 */
	protected String escapeString(final String csvElement) {
		if( StringUtils.isBlank(csvElement)) {
			return "";
		}
		
		StringBuilder work = new StringBuilder();
		// elements with leading/trailing spaces require surrounding quotes if surroundingSpacesNeedQuotes is enabled
		boolean needForEscape = csvElement.charAt(0) == ' ' || csvElement.charAt(csvElement.length() - 1) == ' ';
		
		for( int i = 0; i <= csvElement.length() - 1; i++ ) {
			
			final char c = csvElement.charAt(i);
			
			if( c == delimiter ) {
				needForEscape = true;
				work.append(c);
			} else if( c == quote ) {
				needForEscape = true;
				work.append(quote);
				work.append(quote);
			} else if( c == '\n' ) {
				needForEscape = true;
				work.append('\n');
			} else {
				work.append(c);
			}
		}
		
		// if element contains special characters, escape the
		// whole element with surrounding quotes
		if( needForEscape ) {
			work.insert(0, quote).append(quote);
		}
		
		return work.toString();
	}
}
